from cmath import inf
from collections import defaultdict
from agh_obj import  *
from typing import  Dict, List, Tuple
import csv
import json
import os

MIN = 60

class AGHData():
    def __init__(self) -> None:
        self.vehicle_data:List[List[Vehicle]] = []
        self.gate_data:List[Gate] = []
        self.park_data:List[Park] = []
        self.flight_data:List[Flight] = []

        self.gate_name_to_index:Dict[str, int] = {}
        self.park_name_to_index:Dict[str, int] = {}

        self.cost_data:List[float] = [0]
        self.cost_sum:float = 0
        self.cost_max:float = 0
        self.cost_avg:float = 0

        self.points:List[Tuple[float, float]] = []
        self.edges:List[Tuple[int, int]] = []
        self.lines:List[Tuple[float, float], Tuple[float, float]] = []
        self.paths:Dict[Tuple[int, int], Path] = {}
        Vehicle.generate_random_vehicle_property()

    def is_all_data_prepare(self) -> Tuple[bool, str]:
        wrong_message = ""

        if len(self.gate_data) == 0:
            wrong_message += "没有配置停机位信息；"
        
        if len(self.flight_data) == 0:
            wrong_message += "没有配置航班信息；"
        
        for flight in self.flight_data:
            if flight.gate_name not in self.gate_name_to_index.keys():
                wrong_message += "航班信息与停机位信息不匹配；"
                break

        if wrong_message != "":
            return (False, wrong_message[:-1] + "!")
        else:
            return (True, "")
        
    def load_flight_data_from_file(self, file_name:str) -> Tuple[bool, str]:
        if not(os.path.exists(file_name) and os.path.isfile(file_name)):
            return False, f"{file_name}文件不存在"

        flight_data = []
        try:
            with open(file_name) as f:
                csv_reader = csv.reader(f)
                for row in csv_reader:
                    flight_name, arrival_time, gate_name = row[0], row[1], row[2]
                    if gate_name not in self.gate_name_to_index:
                        return False, f"{gate_name}不存在于当前的停机位信息中" 
                    gate_index = self.gate_name_to_index[gate_name]
                    flight_data.append(Flight(flight_name, arrival_time, gate_name, gate_index))
        except Exception as e:
            return False, "航班信息文件格式错误\n" + str(e)

        if len(flight_data) == 0:
            return False, "文件中没有航班信息"

        self.flight_data = flight_data
        return True, ""
    
    def load_gate_data_from_file(self, file_name:str) -> Tuple[bool, str]:
        if not(os.path.exists(file_name) and os.path.isfile(file_name)):
            return False, f"{file_name}文件不存在"

        with open(file_name) as f:
            data = json.load(f)
        
        try:
            points = [(p[0], p[1]) for p in data["points"]]
            edges = [(e[0], e[1]) for e in data["edges"]]

            gate_data = [Gate(gate["name"], gate["index"], i, points[gate["index"]], gate["is_near_gate"])\
                 for i, gate in enumerate(data["gates"])]

            park_data:List[Park] = []
            vehicle_data = [[] for t in VehicleType]
            vehicle_type = VehicleType._member_map_
            v_set = set()
            for park_index, park in enumerate(data["parks"]):
                vehicles = [[] for t in VehicleType]
                for item in park["vehicles"]:
                    v_type = item["type"] 
                    num = item["num"]
                    if item["type"] not in vehicle_type:
                        return False, f"Park部分数据出现错误，没有找到{v_type}对应的车辆类型"

                    if item["type"] in v_set:
                        return False, f"Park部分数据出现错误，{v_type}车辆类型数据重复了"
                    
                    v_type = vehicle_type[v_type]
                    v_data = vehicle_data[v_type.value]

                    tmp = []
                    start = len(v_data)
                    end = start + num
                    for i in range(start, end):
                        v = Vehicle(v_type, i, park_index)
                        v_data.append(v)
                        tmp.append(v)
                    vehicles[v_type.value] = tmp

                park_data.append(Park(park["name"], park["index"], park_index, points[park["index"]], vehicles))
        except Exception as e:
            return False, "停机位信息json文件格式错误\n" + str(e)

        self.points = points
        self.edges = edges
        self.lines = [(points[u], points[v]) for u, v in edges]

        self.gate_data = gate_data
        self.gate_name_to_index = {g.name:index for index, g in enumerate(gate_data)}

        self.park_data = park_data
        self.park_name_to_index = {p.name:index for index, p in enumerate(park_data)}

        self.vehicle_data = vehicle_data
        
        self.construct_paths()

        return True, ""

    def construct_paths(self):
        edges = self.edges
        points = self.points
        gate_data = self.gate_data
        park_data = self.park_data

        distance = defaultdict(float)
        adj_list = [[] for p in self.points]
        for start, end in edges:
            dis = Path.get_distance(points[start], points[end])
            distance[(start, end)] = dis
            distance[(end, start)] = dis

            adj_list[start].append(end)
            adj_list[end].append(start)

        def get_shortest_path(u, v):
            min_dis = inf
            min_dis_path = []
            visited = {u}

            def dfs(u, dis, path):
                nonlocal min_dis
                nonlocal min_dis_path
                nonlocal visited

                if dis > min_dis:
                    return
                
                if u == v:
                    if dis < min_dis:
                        min_dis = dis
                        min_dis_path = [points[i] for i in path]

                for p in adj_list[u]:
                    if p in visited:
                        continue
                    else:
                        path.append(p)
                        visited.add(p)
                        dfs(p, dis + distance[(u, p)], path)
                        visited.remove(p)
                        path.pop()

            dfs(u, 0, [u])            
            return min_dis_path

        paths = defaultdict(Path)
        
        start_to_end = [(gate_data[i].pos_index, gate_data[j].pos_index, f"{gate_data[i].name}->{gate_data[j].name}") \
            for i in range(len(gate_data)) for j in range(i + 1, len(gate_data))]
        
        start_to_end += [(park.pos_index, gate.pos_index, f"{park.name}->{gate.name}")\
             for park in park_data for gate in gate_data]

        for start, end, discription in start_to_end:
            min_dis_path = get_shortest_path(start, end)
            if len(min_dis_path) == 0:
                paths[(start, end)] = paths[(end, start)] = None
            else:
                paths[(start, end)] = Path(min_dis_path, discription)
                r_discription = "->".join(discription.split('->')[::-1])
                paths[(end, start)] = Path(min_dis_path[::-1], r_discription)

        self.paths = paths

    def load_vehicle_date_from_file(self, file_name:str) -> Tuple[bool, str]:
        if not(os.path.exists(file_name) and os.path.isfile(file_name)):
            return False, f"{file_name}文件不存在"

        try:
            with open(file_name) as f:
                data = json.load(f)
            vehicle_propertys:Dict[VehicleType, VehicleProperty] = {}
            vehicle_type = VehicleType._member_map_
            for item in data:
                v_type = item["type"]
                speed = item["speed"]
                capcity = item["capcity"]
                service_time_cost = item["service_time_cost"]
                cost = item["cost"]
                recover_time_cost = item["recover_time_cost"]

                if v_type not in vehicle_type:
                    return False,  f"Park部分数据出现错误，没有找到{v_type}对应的车辆类型"

                if vehicle_type[v_type] in vehicle_propertys:
                    return False, f"Park部分数据出现错误，{v_type}车辆类型数据重复了"
                
                vehicle_propertys[vehicle_type[v_type]] = \
                                VehicleProperty(speed, service_time_cost, capcity, cost, recover_time_cost)
            
            Vehicle.property_of_type = vehicle_propertys
            return True, ""

        except Exception as e:
            return False, "车辆配置json文件格式错误" + str(e)       

    def get_distance_between_gates(self, index_1:int, index_2:int) -> int:
        u = self.gate_data[index_1].pos_index
        v = self.gate_data[index_2].pos_index
        if u == v:
            return 0
        return self.paths[(u, v)].length

    def get_distance_park_to_gate(self, park_index:int,  gate_index:int) -> int:
        u = self.gate_data[gate_index].pos_index
        v = self.park_data[park_index].pos_index

        if self.paths[(u, v)] == None:
            return -1
        return self.paths[(u, v)].length

    def get_path_between_gates(self, index_1:int, index_2:int) -> Path:
        u = self.gate_data[index_1].pos_index
        v = self.gate_data[index_2].pos_index
        if u == v:
            return None
        return self.paths[(u, v)]

    def get_path_park_to_gate(self, gate_index:int, park_index:int, reversed = False) -> Path:
        u = self.gate_data[gate_index].pos_index
        v = self.park_data[park_index].pos_index
        if reversed:
            return self.paths[(u, v)]
        return self.paths[(v, u)]

    def get_vehicle_table_data(self):
        if len(self.vehicle_data) == 0:
            return []
        table = []
        for t in VehicleType:
            name = VEHICLE_NAMES[t.value]
            total_num = len(self.vehicle_data[t.value])
            serving_num = len(list(filter(lambda v: v.state != VehicleState.Standby and v.state != VehicleState.Recovering, self.vehicle_data[t.value])))
            table.append((name, total_num, serving_num))
        return table

    def get_gate_table_data(self):
        table = []
        gate_in_working = list(filter(lambda gate: gate.is_working, self.gate_data))
        gate_in_working.sort(key=lambda g: g.start_serving_time)
        for gate in gate_in_working:
            row = [gate.name, gate.flight.name, gate.flight.arrival_time[-5:]]
            for i, v in enumerate(gate.vehicles):
                if gate.is_near_gate and i == VehicleType.ShuttleBus.value:
                    row.append("无需服务")
                    continue

                if isinstance(v, Vehicle):
                    row.append((v.state, int(v.get_progress_value() * 100)))
                elif gate.vehicles_work_done[i]:
                    row.append((VehicleState.Complete, 0))
                else:
                    row.append("暂无分配")
            table.append(row)
        return table

    def get_flight_table_data(self, num:int = -1) -> List[Tuple[str, str, str]]:
        table = []
        num = len(self.flight_data) if num == -1 else num
        cur_num = index = 0
        while cur_num < num and index < len(self.flight_data):
            flight = self.flight_data[index]
            if not flight.is_done:
                table.append((flight.name, flight.gate_name, flight.arrival_time[-5:]))
                cur_num += 1
            index += 1 
        return table

    def refresh_data(self):
        for vehicles in self.vehicle_data:
            for vehicle in vehicles:
                vehicle.refresh()
        
        for filght in self.flight_data:
            filght.refresh()
        
        for gate in self.gate_data:
            gate.refresh()

        self.cost_data = []
        self.cost_sum = 0
        self.cost_avg = 0
        self.cost_max = 0
